import logging
import socket
import string

import util.json as json


"""
Client API
"""

log = logging.getLogger('api')

#
# Constants
#
OK = 0

# The following should be bitmask'able
OBSOLETE = 1 << 1
CONFLICT = 1 << 2

def err2str(err):
    t = { OBSOLETE : 'OBSOLETE', CONFLICT : 'CONFLICT' }
    s = []
    for bitmask in t.keys():
        if bitmask & err:
            s.append(t[bitmask])
    return string.join(s, ', ')

class DbError(Exception):
    """Error raised when something is wrong with the database
    usage."""
    pass

class ApiError(Exception):
    pass

class IPCError(Exception):
    pass

class ClientConfig:
    """Client configuration options."""
    def __init__(self, app_name = None):
        self.app_name_ = app_name

class NeonAPI:
    """NeonAPI consumed by the client stub. This is the interface to
    the application."""

    def __init__(self):
        self.server_addr_ = None
        self.config_ = None
	self.id_     = None
        self.socket_ = None
        self.types_ = {}

    def connect(self, server_addr, client_config):
        """server_addr : ip, port pair

        protocol:
            c->s ClientConfig
            s->c True
        """
        assert self.socket_ is None
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        try:
            s.connect(server_addr)
        except socket.error:
            raise IPCError("Can't connect to API server: " + str(server_addr))
        
        self.server_addr_ = server_addr
        self.config_ = client_config
        self.socket_ = s
        msg = json.write({'name' : client_config.app_name_})
        self.send_msg_(msg)
        self.recv_server_init_()
        log.info('API client connected to API server ' + str(self.server_addr_))

    def send_msg_(self, msg):
        """Send a message. Message must be a JSON formatted string."""
        self.socket_.sendall(msg + '\n')

    def recv_msg_(self):
        """Returns a newline deliminited message from the
        server. Return should be a JSON formatted string."""
        s = ''
        c = ' '
        while c != '\n' or c != '':
            c = self.socket_.recv(1)
            if c == '\n':
                break
            s = s + c
        return s
        
    def recv_server_init_(self):
        msg = self.recv_msg_()
        msg = json.read(msg)
	self.id_ = msg['id']
	log.debug('id = %s' % self.id_)
	for t in msg['typenames']:
	    self.types_[t] = None
	    log.debug('type = %s' % t)

    def get_type(self, t):
	# XXX/bowei -- get the type definition on demand
        return self.types_[t]

    def close(self):
        """Close connection to API server."""
        self.socket_.close()
        log.info('API client closed connection to API server')
        self.server_addr_ = None
        self.config_ = None
        self.socket_ = None

    # XXX/bowei -- some reconfigure call
    
#     def get_server_config(self):
#         self.send_msg_('get_server_config')
#         self.send_msg_('null')
#         msg = self.recv_msg_()
#         return json.read(msg)
        
#     def get_stats(self):
#         self.send_msg_('get_stats')
#         self.send_msg_('null')
#         msg = self.recv_msg_()
#         assert msg == 'null'
        
#     def open_share(self, sharename, flags):
#         self.send_msg_('open_share')
#         self.send_msg_(json.write({'sharename' : sharename,
#                                    'flags'     : flags}))
#         msg = self.recv_msg_()
#         # XXX/bowei -- return value

#     def close_share(self, share_handle):
#         self.send_msg_('close_share')
#         self.send_msg_(json.write({'share_handle' : share_handle}))
#         msg = self.recv_msg_()
#         # XXX/bowei -- return value

    def new_atom(self, typename):
        return neon.atom.Atom(typename)

    def get_atom(self, guid):
        log.debug('get_atom(%s)' % (guid))
        msg = {'req' : 'get_atom', 'guid' : guid.to_raw()}
        self.send_msg_(json.write(msg))
        r = self.recv_msg_()
        log.debug('get_atom(%s) - %s' % (guid, r))        
        return json.read(r)

    def put_atom(self, atom, vv):
        """@return A tuple containing the return code and the assigned
                   guid of the atom return code: api.OK, api.OBSOLETE,
                   api.CONFLICT.
           @errors no such type class, illegal atom, illegal version,
                   neon internal error
        """
        log.debug('put_atom(%s, %s)' % (atom, vv))
        assert atom.is_valid()
        msg = {'req'  : 'put_atom',
               'atom' : atom.to_raw(),
               'vv'   : str(vv)}
        self.send_msg_(json.write(msg))
        r = self.recv_msg_()
        # XXX/bowei -- handle errors
        log.debug('put_atom(%s, %s) - %s' % (atom, vv, r))
        return json.read(r)
    
    def del_atom(self, guid):
        # XXX/bowei -- write me
        raise NotImplementedError()
        
    def query_db(self, query):
        """@return A tuple containing a list of the fieldnames and the
        rows of the fields."""
        log.debug('query_db(%s)' % (query))
        msg = {'req'   : 'query_db',
               'query' : query}
        self.send_msg_(json.write(msg))
        r = self.recv_msg_()
        log.debug('query_db(%s) - %s' % (query, r))
        return json.read(r)
        # XXX/bowei -- handle errors here

    def test(self):
        log.debug('test(%s)')
        msg = {'req'   : 'test',
               'query' : ''}
        self.send_msg_(json.write(msg))
        r = self.recv_msg_()
        log.debug('test(%s) - %s' % (query, r))
        return json.read(r)
        
    def get_events(self):
        raise NotImplementedError()
